package chloroform

import _root_.util.{ParameterizedTypeWrapper, GenericAnalysisUtils}
import scaladuct._
import java.lang.String
import java.lang.reflect.{Type, Constructor}
import collection.immutable.List

/**
 * <p>
 * Created by IntelliJ IDEA.
 * User: zslajchrt
 * Date: Feb 19, 2011
 * Time: 8:57:29 PM
 */

@SerialVersionUID(1L)
class ClasspathConfigPageFactory[X] extends {
val id = "chloroform.ClasspathConfigPageFactory"
} with ConfigPage[ConfigPageFactory[ConfigPageFactory[X]]] {
  type Cookie = Terminal

  def getCookie() = Terminal

  def consume(cookie: Cookie) = NewStarter(new Starter[ConfigPageFactory[ConfigPageFactory[X]]] {
    def newInstance(): ConfigPageFactory[ConfigPageFactory[X]] = {
      new ConfigPageFactory[ConfigPageFactory[X]] {
        def createConfigPage(pageInstanceName: String, lookup: LookupFunctions.LookupFunction, initArgs: AnyRef*) = {
          PhaseMethodInterceptor(classOf[ClasspathConfigPage[X]]).page.asInstanceOf[ConfigPage[ConfigPageFactory[X]]]
        }
      }
    }

    def destroy(instance: ConfigPageFactory[ConfigPageFactory[X]]) = null

  })

}

object ClasspathConfigUtils {

  def getActualTypeParameter(pageClass: Class[_]): Type = {
    val productTypeActualParams = GenericAnalysisUtils.findActualTypeParameters(pageClass, classOf[ConfigPage[_]]) match {
      case Some(genAncestor) => Some(genAncestor)
      case None => GenericAnalysisUtils.findActualTypeParameters(pageClass, classOf[Starter[_]])
    }

    productTypeActualParams match {
      case None => error("Config source class " + pageClass.getName + " not found does not implements neither ConfigPage nor Starter interface")

      case Some(actualTypeArgs) => {
        actualTypeArgs(0)
      }
    }
  }

}

@SerialVersionUID(1L)
class StarterWithProductType[X](decorStarter: Starter[X], pageClass: Class[_]) extends StarterDecorator[X](decorStarter) with HasProductType {
  def getProductType() = {
    // the decorated page has priority
    Extensible.extension(decorStarter, classOf[HasProductType]) match {
      case None => ClasspathConfigUtils.getActualTypeParameter(pageClass)
      case Some(hasType) => hasType.getProductType
    }
  }
}

private class PageDecoratorForPhaseStarterPage[X](decor: ConfigPage[X], pageClass: Class[_])
  extends ConfigPageDecorator[X](decor) with HasProductType {

  override def consume(cookie: Cookie) = {
    super.consume(cookie) match {
      case NewStarter(starter) => NewStarter(new StarterWithProductType(starter, pageClass))
      case NextPage(nextPage) => NextPage(new PageDecoratorForPhaseStarterPage(nextPage, pageClass))
    }
  }

  def getProductType() = {
    // the decorated page has priority
    decor.extension(classOf[HasProductType]) match {
      case None => ClasspathConfigUtils.getActualTypeParameter(pageClass)
      case Some(hasType) => hasType.getProductType
    }
  }
}

@SerialVersionUID(1L)
class ClasspathConfigPage[X] extends Starter[ConfigPageFactory[X]] with HasProductType {

  private var pageName: String = null
  private var pageClass: Class[_] = null
  private var productType: Type = null

  def setPageName(pageName: String) = {
    this.pageName = pageName
  }

  def newInstance() = phaseCollectParameters(null)

  def getProductType() = productType

  private def getProductTypeFromPageClass(): Type =
    ClasspathConfigUtils.getActualTypeParameter(pageClass)

  def phaseCollectParameters(className: String): ConfigPageFactory[X] = {
    // load the page/starter class
    pageClass = Thread.currentThread.getContextClassLoader.loadClass(className)
    productType = SerializableParameterizedType.transform(
      new ParameterizedTypeWrapper(null, classOf[ConfigPage[_]], Array(getProductTypeFromPageClass())))

    if (classOf[Starter[X]].isAssignableFrom(pageClass))
      newPhasePage(pageClass.asInstanceOf[Class[Starter[X]]])
    else if (classOf[ConfigPage[X]].isAssignableFrom(pageClass)) {
      newPage(pageClass.asInstanceOf[Class[ConfigPage[X]]])
    } else {
      throw new IllegalArgumentException("Page class is neither a starter nor a config page class:" + className)
    }
  }

  def newPhasePage(pageClass: Class[Starter[X]]): ConfigPageFactory[X] = {

    @SerialVersionUID(1L)
    class PhaseMethodPageFactory extends ConfigPageFactory[X] {
      def createConfigPage(pageInstanceName: String, lookup: LookupFunctions.LookupFunction, initArgs: AnyRef*): ConfigPage[X] = {

        val interceptor: PhaseMethodInterceptor[X] = PhaseMethodInterceptor(pageInstanceName + ".", pageClass,
          initArgs: _*)(lookup)
        val firstPage = interceptor.phase.firstPage

        new PageDecoratorForPhaseStarterPage[X](firstPage, pageClass)
      }
    }

    new PhaseMethodPageFactory
  }

  def newPage(pageClass: Class[ConfigPage[X]]): ConfigPageFactory[X] = {

    @SerialVersionUID(1L)
    class StandardPageFactory extends ConfigPageFactory[X] {
      def createConfigPage(pageInstanceName: String, lookup: LookupFunctions.LookupFunction,
                           initArgs: AnyRef*): ConfigPage[X] = {

        val constructors: Array[Constructor[ConfigPage[X]]] =
          pageClass.getConstructors.asInstanceOf[Array[Constructor[ConfigPage[X]]]]

        require(constructors.length == 1, pageClass.getName +
          " page class must have just one public constructor (" + constructors.length + ")")
        val firstPage = if (constructors(0).getParameterTypes.isEmpty) {
          constructors(0).newInstance()
        } else {
          val argList: List[AnyRef] = pageInstanceName :: initArgs.toList
          constructors(0).newInstance(argList.toArray: _*)
        }

        new PageDecoratorForPhaseStarterPage(firstPage, pageClass)
      }

    }

    new StandardPageFactory

  }

  def destroy(instance: ConfigPageFactory[X]) = null


}